﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using l1cs.server.model.Instance;
using l1cs.server.model.map;
using l1cs.server.model;
using l1cs.server.serverpackets;
using l1cs.server.model.skill;

namespace l1cs.server.utils
{
    public class Teleportation
    {

        private static Logger _log = Logger.getLogger( typeof( Teleportation ).FullName );

        private static Random _random = new Random();

        private Teleportation() { }

        public static void staticTeleportation( L1PcInstance pc )
        {
            if ( pc.isDead() || pc.isTeleport() )
            {
                return;
            }

            int x = pc.getTeleportX();
            int y = pc.getTeleportY();
            short mapId = pc.getTeleportMapId();
            int head = pc.getTeleportHeading();

            // テレポート先が不正であれば元の座標へ(GMは除く)
            L1Map map = L1WorldMap.getInstance().getMap( mapId );

            if ( !map.isInMap( x, y ) && !pc.isGm() )
            {
                x = pc.getX();
                y = pc.getY();
                mapId = pc.getMapId();
            }

            pc.setTeleport( true );

            L1Clan clan = L1World.getInstance().getClan( pc.getClanname() );
            if ( clan != null )
            {
                if ( clan.getWarehouseUsingChar() == pc.getId() )
                { // 自キャラがクラン倉庫使用中
                    clan.setWarehouseUsingChar( 0 ); // クラン倉庫のロックを解除
                }
            }

            L1World.getInstance().moveVisibleObject( pc, mapId );
            pc.setLocation( x, y, mapId );
            pc.setHeading( head );
            pc.sendPackets( new S_MapID( pc.getMapId(), pc.getMap().isUnderwater() ) );

            if ( pc.isReserveGhost() )
            { // ゴースト状態解除
                pc.endGhost();
            }
            if ( pc.isGhost() || pc.isGmInvis() )
            {
            }
            else if ( pc.isInvisble() )
            {
                pc.broadcastPacketForFindInvis( new S_OtherCharPacks( pc, true ),
                        true );
            }
            else
            {
                pc.broadcastPacket( new S_OtherCharPacks( pc ) );
            }
            pc.sendPackets( new S_OwnCharPack( pc ) );

            pc.removeAllKnownObjects();
            pc.sendVisualEffectAtTeleport(); // クラウン、毒、水中等の視覚効果を表示
            pc.updateObject();
            // spr番号6310, 5641の変身中にテレポートするとテレポート後に移動できなくなる
            // 武器を着脱すると移動できるようになるため、S_CharVisualUpdateを送信する
            pc.sendPackets( new S_CharVisualUpdate( pc ) );

            pc.killSkillEffectTimer( L1SkillId.MEDITATION );
            pc.setCallClanId( 0 ); // コールクランを唱えた後に移動すると召喚無効

            /*
             * subjects ペットとサモンのテレポート先画面内へ居たプレイヤー。
             * 各ペット毎にUpdateObjectを行う方がコード上ではスマートだが、
             * ネットワーク負荷が大きくなる為、一旦Setへ格納して最後にまとめてUpdateObjectする。
             */
            HashSet<L1PcInstance> subjects = new HashSet<L1PcInstance>();
            subjects.Add( pc );

            if ( !pc.isGhost() )
            {
                if ( pc.getMap().isTakePets() )
                {
                    // ペットとサモンも一緒に移動させる。
                    foreach ( L1NpcInstance petNpc in pc.getPetList().Values )
                    {
                        // テレポート先の設定
                        L1Location loc = pc.getLocation().randomLocation( 3, false );
                        int nx = loc.getX();
                        int ny = loc.getY();
                        if ( pc.getMapId() == 5125 || pc.getMapId() == 5131
                                || pc.getMapId() == 5132 || pc.getMapId() == 5133
                                || pc.getMapId() == 5134 )
                        { // ペットマッチ会場
                            nx = 32799 + _random.Next( 5 ) - 3;
                            ny = 32864 + _random.Next( 5 ) - 3;
                        }
                        teleport( petNpc, nx, ny, mapId, head );
                        if ( petNpc is L1SummonInstance )
                        { // サモンモンスター
                            L1SummonInstance summon = (L1SummonInstance)petNpc;
                            pc.sendPackets( new S_SummonPack( summon, pc ) );
                        }
                        else if ( petNpc is L1PetInstance )
                        { // ペット
                            L1PetInstance pet = (L1PetInstance)petNpc;
                            pc.sendPackets( new S_PetPack( pet, pc ) );
                        }

                        foreach ( L1PcInstance visiblePc in L1World.getInstance()
                                .getVisiblePlayer( petNpc ) )
                        {
                            // テレポート元と先に同じPCが居た場合、正しく更新されない為、一度removeする。
                            visiblePc.removeKnownObject( petNpc );
                            subjects.Add( visiblePc );
                        }
                    }

                    // マジックドールも一緒に移動させる。
                    foreach ( L1DollInstance doll in pc.getDollList().Values )
                    {
                        // テレポート先の設定
                        L1Location loc = pc.getLocation().randomLocation( 3, false );
                        int nx = loc.getX();
                        int ny = loc.getY();

                        teleport( doll, nx, ny, mapId, head );
                        pc.sendPackets( new S_DollPack( doll, pc ) );

                        foreach ( L1PcInstance visiblePc in L1World.getInstance()
                                .getVisiblePlayer( doll ) )
                        {
                            // テレポート元と先に同じPCが居た場合、正しく更新されない為、一度removeする。
                            visiblePc.removeKnownObject( doll );
                            subjects.Add( visiblePc );
                        }
                    }
                }
                else
                {
                    foreach ( L1DollInstance doll in pc.getDollList().Values )
                    {
                        // テレポート先の設定
                        L1Location loc = pc.getLocation().randomLocation( 3, false );
                        int nx = loc.getX();
                        int ny = loc.getY();

                        teleport( doll, nx, ny, mapId, head );
                        pc.sendPackets( new S_DollPack( doll, pc ) );

                        foreach ( L1PcInstance visiblePc in L1World.getInstance()
                                .getVisiblePlayer( doll ) )
                        {
                            // テレポート元と先に同じPCが居た場合、正しく更新されない為、一度removeする。
                            visiblePc.removeKnownObject( doll );
                            subjects.Add( visiblePc );
                        }
                    }
                }
            }

            foreach ( L1PcInstance updatePc in subjects )
            {
                updatePc.updateObject();
            }

            pc.setTeleport( false );

            if ( pc.hasSkillEffect( L1SkillId.WIND_SHACKLE ) )
            {
                pc.sendPackets( new S_SkillIconWindShackle( pc.getId(),
                        pc.getSkillEffectTimeSec( L1SkillId.WIND_SHACKLE ) ) );
            }
        }

        private static void teleport( L1NpcInstance npc, int x, int y, short map,
                int head )
        {
            L1World.getInstance().moveVisibleObject( npc, map );

            L1WorldMap.getInstance().getMap( npc.getMapId() ).setPassable( npc.getX(),
                    npc.getY(), true );
            npc.setX( x );
            npc.setY( y );
            npc.setMap( map );
            npc.setHeading( head );
            L1WorldMap.getInstance().getMap( npc.getMapId() ).setPassable( npc.getX(),
                    npc.getY(), false );
        }

    }

}
